/*
 * \brief  Container that hosts Init's children
 * \author Norman Feske
 * \date   2008-03-22
 */

/*
 * Copyright (C) 2008-2009 Norman Feske
 * Genode Labs, Feske & Helmuth Systementwicklung GbR
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

#ifndef _INCLUDE__INIT__NEST_H_
#define _INCLUDE__INIT__NEST_H_

#include <base/env.h>
#include <base/printf.h>
#include <base/snprintf.h>
#include <base/lock.h>
#include <base/service.h>
#include <util/xml_node.h>
#include <ram_session/connection.h>
#include <cpu_session/connection.h>
#include <util/list.h>
#include <init/child.h>
#include <init/child_config.h>

namespace Init {

	class Nest
	{
		private:

			/*
			 * List of children
			 *
			 * Access to the children list from different threads
			 * must be synchronized via the children lock.
			 */
			Genode::Lock              _children_lock;
			Genode::List<Init::Child> _children;

			Genode::Cap_session *_cap_session;

			/*
			 * Database with services as announced by our children
			 */
			Genode::Remote_service_pool _remote_services;

			/**
			 * Check if a program with the specified name already exists
			 */
			bool _child_name_exists(const char *name)
			{
				Init::Child *c = _children.first();

				for ( ; c; c = c->Genode::List<Init::Child>::Element::next())
					if (Genode::strcmp(c->name(), name) == 0)
						return true;

				return false;
			}

			/**
			 * Create a unique name based on the filename
			 *
			 * If a program with the filename as name already exists, we
			 * append a counting number as suffix.
			 */
			void _unique_child_name(const char *filename,
			                        char *dst, Genode::size_t dst_len)
			{
				using namespace Genode;

				Lock::Guard lock_guard(_children_lock);

				char buf[32];
				char suffix[8];
				suffix[0] = 0;

				for (int cnt = 1; true; cnt++) {

					/* build program name composed of filename and numeric suffix */
					snprintf(buf, sizeof(buf), "%s%s", filename, suffix);

					/* if such a program name does not exist yet, we are happy */
					if (!_child_name_exists(buf)) {
						strncpy(dst, buf, dst_len);
						return;
					}

					/* increase number of suffix */
					snprintf(suffix, sizeof(suffix), ".%d", cnt + 1);
				}
			}

		public:

			/**
			 * Constructor
			 */
			Nest(Genode::Cap_session *cap_session):
				_cap_session(cap_session) { }

			/**
			 * Start program with specified configuration
			 */
			int add_child(Genode::Xml_node start_node)
			{
				using namespace Genode;

				char filename[32];
				try {
					start_node.sub_node("filename").content(filename, sizeof(filename));
				} catch (Genode::Xml_node::Nonexistent_subnode) {
					printf("Warning: Missing valid <filename> in config-file entry.\n");
					return -1;
				}

				size_t ram_quota = 0;
				try {
					start_node.sub_node("ram_quota").read_size_t(&ram_quota);
				} catch (Genode::Xml_node::Nonexistent_subnode) {
					printf("Warning: Missing valid <ram_quota> in config-file entry.\n");
					return -1;
				}

				/*
				 * If the configured quota exceeds our own quota, we donate
				 * all remaining quota to the child but we need to count in
				 * our allocation of the child meta data from the heap.
				 * Hence, we preserve 64K of our own quota.
				 */
				if (ram_quota > env()->ram_session()->avail() - 64*1024) {
					ram_quota = env()->ram_session()->avail() - 64*1024;
					printf("Warning: Specified quota for \"%s\" exceeds available quota.\n"
					       "         Proceeding with a quota of %zd bytes.\n", filename, ram_quota);
				}

				printf("starting %s with quota=%ld\n", filename, ram_quota);

				char unique_name[64];
				_unique_child_name(filename, unique_name, sizeof(unique_name));
				printf("using unique child name \"%s\"\n", unique_name);

				/* lookup executable elf binary */
				Dataspace_capability file_cap;
				Rom_connection rom(filename, unique_name);
				try {
					file_cap = rom.dataspace();
				} catch (Rom_file_missing) {
					printf("Warning: Could not find file \"%s\" at ROM service.\n", filename);
					return -1;
				}

				/* create ram session for child with some of our own quota */
				Ram_connection ram(unique_name);
				ram.ref_account(env()->ram_session_cap());
				env()->ram_session()->transfer_quota(ram.cap(), ram_quota);

				/* create cpu session for child */
				Cpu_connection cpu(unique_name);

				if (!ram.cap().valid() || !cpu.cap().valid()) {
					PWRN("ram or cpu session not valid");
					return -2;
				}

				/* do not close the sessions when leaving the scope */
				rom.on_destruction(Rom_connection::KEEP_OPEN);
				ram.on_destruction(Ram_connection::KEEP_OPEN);
				cpu.on_destruction(Cpu_connection::KEEP_OPEN);

				/* obtain configuration for the child */
				Init::Child_config *config = new (env()->heap())
					Init::Child_config(ram.cap(), start_node);

				Init::Child *child = new (env()->heap())
					Init::Child(unique_name, file_cap, ram.cap(), cpu.cap(),
					            _cap_session, &_remote_services, config->dataspace());

				child->finalize_construction();

				Lock::Guard lock_guard(_children_lock);
				_children.insert(child);
				return 0;
			}
	};
}

#endif /* _INCLUDE__INIT__NEST_H_ */
